﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class ClustersGenerator
    {
        const int DEFAULT_NUM_OF_CLUSTERS = 2;
        const string DEFAULT_CLUSTER_METHOD = "SimpleKMeans";
        const int DEFAULT_SEED = 10;

        public int NumOfClusters { get; set; }
        public string ClusterMethod { get; set; }
        public int Seed { get; set; }


        // default constructor
        public ClustersGenerator()
        {
            NumOfClusters = DEFAULT_NUM_OF_CLUSTERS;
            ClusterMethod = DEFAULT_CLUSTER_METHOD;
            Seed = DEFAULT_SEED;
        }



        /// <summary>
        /// generating the clusters and returning the results as a dictionary, where the key is the cluster_number
        /// and the value is a list of documents that belong to that cluster.
        /// </summary>
        /// <param name="allDocuments">dictionary where the key is doc name and value is a list of weights</param>
        /// <param name="features">a list of the feature's hash values</param>
        public Dictionary<int, List<string>> GenerateClusters(Dictionary<string, List<double>> documents, List<int> features)
        {
            this.CreateArffFile(documents,features);
            this.CreateBatchFile_SimpleFormat();

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "generate-clusters.bat"
                }
            };
            process.Start();
            process.WaitForExit();

            return this.ExtractClusteresResults();
        }


        /// <summary>
        /// Writes the initial CSV file: The features are the n-grams, the instances are the documents
        /// </summary>
        /// <param name="allDocuments">dictionary where the key is doc name and value is a list of weights</param>
        /// <param name="features">a list of the feature's hash values</param>
        public void CreateCSVFile(Dictionary<string, List<double>> allDocuments, List<int> features)
        {
            using (CsvFileWriter writer = new CsvFileWriter("documents-data.csv"))
            {
                CsvRow row = new CsvRow();

                foreach (int feature in features)
                {
                    row.Add(String.Format("{0}", feature));
                }
                writer.WriteRow(row);

                foreach (KeyValuePair<string, List<double>> doc in allDocuments)
                {
                    CsvRow row1 = new CsvRow();
                    foreach (double weight in doc.Value)
                    {
                        row1.Add(String.Format("{0}", weight));
                    }
                    
                    writer.WriteRow(row1);
                }
            }
        }


        /// <summary>
        /// Writes the initial arff file: The features are the n-grams, the instances are the documents
        /// </summary>
        /// <param name="allDocuments">dictionary where the key is doc name and the value is a list of weights</param>
        /// <param name="features">a list of the feature's hash values</param>
        public void CreateArffFile(Dictionary<string, List<double>> allDocuments, List<int> features)
        {
            StringBuilder sbArffFile = new StringBuilder();
            string docNames = "";
            sbArffFile.Append("@RELATION documents");
            sbArffFile.AppendLine();
            sbArffFile.AppendLine();

            sbArffFile.Append("@ATTRIBUTE ");
            sbArffFile.Append("DocName {");

            foreach (KeyValuePair<string, List<double>> doc in allDocuments)
            {
                docNames += doc.Key + ",";
            }

            docNames = docNames.TrimEnd(',');
            sbArffFile.Append(docNames);
            sbArffFile.Append("}");

            sbArffFile.AppendLine();


            foreach (int feature in features)
            {
                sbArffFile.Append("@ATTRIBUTE ");
                sbArffFile.Append(+feature);
                sbArffFile.Append(" NUMERIC ");
                sbArffFile.AppendLine();
            }

            sbArffFile.AppendLine();
            sbArffFile.Append("@DATA");
            sbArffFile.AppendLine();
            sbArffFile.AppendLine();

            foreach (KeyValuePair<string, List<double>> doc in allDocuments)
            {
                int i = 0;
                sbArffFile.Append(doc.Key + ",");
                foreach (double weight in doc.Value)
                {
                    sbArffFile.Append(+weight);
                    if (i < doc.Value.Count - 1)
                        sbArffFile.Append(",");

                    i++;
                }
                i = 0;

                sbArffFile.AppendLine();
                //sbArffFile.AppendLine();

            }
            if (File.Exists("documents-data.arff"))
                File.Delete("documents-data.arff");

            FileStream fs = new FileStream("documents-data.arff", FileMode.CreateNew, FileAccess.Write);

            Byte[] bytes = System.Text.Encoding.ASCII.GetBytes(sbArffFile.ToString());

            foreach (Byte b in bytes)
                fs.WriteByte(b);

            fs.Close();
        }


       
        /// <summary>
        /// Generates the ".bat" file that will be used to activate the weka and generate the clusters
        /// the format of this result file is more complicated and has details on every cluster 
        /// </summary>
        public void CreateBatchFile()
        {
            string dir = Directory.GetCurrentDirectory();
            string text = "java -classpath \"C:\\Program Files (x86)\\Weka-3-6\\weka.jar\""
                         + " weka.clusterers." + ClusterMethod + " -N " + NumOfClusters + " -S " + Seed
                         + " -t documents-data.arff" + " -c first"
                         + " > " + dir + "\\clusters-results.arff";
            StreamWriter sw = new StreamWriter("generate-clusters.bat");
            sw.Write(text);
            sw.Close();
        }


        /// <summary>
        /// Generates the ".bat" file that will be used to activate the weka and generate the clusters.
        /// The format of this result file is more simple and just adds the cluster number as a nominal 
        /// attribute to the data.
        /// </summary>
        public void CreateBatchFile_SimpleFormat()
        {
            string dir = Directory.GetCurrentDirectory();
            string text = "java -classpath \"C:\\Program Files\\Weka-3-7\\weka.jar\""
                         + " weka.filters.unsupervised.attribute.AddCluster"
                         + " -W \"weka.clusterers.SimpleKMeans -N " + NumOfClusters + " -S " + Seed + "\""
                         + " -I first -i documents-data.arff" + " > " + dir + "\\clusters-results.arff";
            StreamWriter sw = new StreamWriter("generate-clusters.bat");
            sw.Write(text);
            sw.Close();
        }


        /// <summary>
        /// Reads the clusters-results file (ONLY the simple format) to a dictionary, where the key is the cluster
        /// number and the value  is a list of documents that belong to that cluster. Returns the dictionary. 
        /// </summary>
        public Dictionary<int, List<string>> ExtractClusteresResults()
        {
            bool toRead = false;
            string line;
            Dictionary<int, List<string>> clusters = new Dictionary<int, List<string>>();
            StreamReader sr = new StreamReader(Directory.GetCurrentDirectory() + "/clusters-results.arff");

            while (((line = sr.ReadLine()) != null))
            {
                if (line.Equals("@data"))
                {
                    toRead = true;
                    continue;
                }
                else if (!toRead)
                    continue;
                else if (!line.Equals(""))
                {
                    List<string> docName = new List<string>();
                    string[] docProps = line.Split(',');
                    string clusterName = docProps[docProps.Length - 1];
                    int clusterNum = int.Parse(clusterName.Substring(clusterName.Length - 1, 1));
                    string name = docProps[0];

                    if (!clusters.ContainsKey(clusterNum))
                        clusters.Add(clusterNum, new List<string>());

                    clusters[clusterNum].Add(name);
                }
            }
            sr.Close();
            return clusters;
            
        }



        
    }
}
